/**
 * 
 */
package net.phoenix.repository.query.xpath;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;

import net.phoenix.repository.dao.QDBName;

import org.apache.xml.dtm.Axis;
import org.jaxen.JaxenException;
import org.jaxen.saxpath.Operator;
import org.jaxen.saxpath.XPathHandler;

/**
 * @author lixf
 * 
 */
public class XPath2SQL implements XPathHandler {
	
	private ConditionStack conditions; // where条件
	private Stack<String> properties;// 属性，用来构建source
//	private NodeDefinition nodefinition;// 当前的NodeDefinition；
	private List<Value> parameters;
	private Session session;
	private Node parent;
	private ValueFactory valueFactory;
	private int stepNo; // 第n个Step;
	private String condition;

	/**
	 * Constructor
	 * 
	 * @throws RepositoryException
	 */
	public XPath2SQL(Node parent) throws RepositoryException {
		this.parent = parent;
		this.session = parent.getSession();
		this.valueFactory = this.session.getValueFactory();
		this.parameters = new ArrayList<Value>();
		this.conditions = new ConditionStack();
//		this.nodefinition = this.parent.getDefinition();
		this.properties = new Stack<String>();
	}

	/*
	public Query toQuery() throws RepositoryException {
		@SuppressWarnings("deprecation")
		Query query = this.parent.getSession().getWorkspace().getQueryManager().createQuery(this.getSQL(), Query.SQL);
		for (int i = 0; i < parameters.size(); i++) {
			query.bindValue("" + (i + 1), parameters.get(i));
		}
		return query;
	}
	*/

	/**
	 * Retrieve the simplified Jaxen XPath expression tree.
	 * 
	 * <p>
	 * This method is only valid once <code>XPathReader.parse(...)</code>
	 * successfully returned.
	 * </p>
	 * 
	 * @return the XPath expression tree
	 */
	public String getQuerySQL() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("SELECT * FROM node ");
		for (String property : this.properties) {
			String table = QDBName.encode(property);
			buffer.append("LEFT JOIN " + table);
			buffer.append(" ON " + table + ".node_id=node.pkid ");
		}
		if (this.condition.length() > 0) {
			buffer.append(" WHERE ");
			buffer.append(this.condition);
		}
		buffer.append(" ORDER BY node.vindex ");
		return buffer.toString();

	}
	/**
	 * Retrieve the simplified Jaxen XPath expression tree.
	 * 
	 * <p>
	 * This method is only valid once <code>XPathReader.parse(...)</code>
	 * successfully returned.
	 * </p>
	 * 
	 * @return the XPath expression tree
	 */
	public String getCountSQL() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("SELECT count(pkid) FROM node ");
		for (String property : this.properties) {
			String table = QDBName.encode(property);
			buffer.append("LEFT JOIN " + table);
			buffer.append(" ON " + table + ".node_id=node.pkid ");
		}
		if (this.condition.length() > 0) {
			buffer.append(" WHERE ");
			buffer.append(this.condition);
		}
		buffer.append(" ORDER BY node.vindex ");
		return buffer.toString();

	}

	public List<Value> getParameters() {
		return this.parameters;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jaxen.saxpath.XPathHandler#startXPath()
	 */
	@Override
	public void startXPath() {
		this.conditions.pushFrame();
		this.stepNo = 0;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jaxen.saxpath.XPathHandler#endXPath()
	 */
	@Override
	public void endXPath() throws JaxenException {
		this.condition = this.conditions.popCondition();
		this.conditions.popFrame();
	}

	/*
	 * Path Expr
	 * 
	 * PathExpr ::= LocationPath | FilterExpr | FilterExpr /
	 * RelativeLocationPath | FilterExpr // RelativeLocationPath If the current
	 * stack-frame has two items, it's a FilterExpr and a LocationPath (of some
	 * flavor). If the current stack-frame has one item, it's simply a
	 * FilterExpr, and more than likely boils down to a primary expr of some
	 * flavor. But that's for another method... 如果是第二、三..级的pathExpr，则将<ol> <li>.
	 * 前面的结果作为父节点的检索条件<li>. <li>. 清空properties列表。<li>. </ol>
	 * 
	 * @see org.jaxen.saxpath.XPathHandler#startPathExpr()
	 */
	@Override
	public void startPathExpr() throws JaxenException {
		this.conditions.pushFrame();
	}

	@Override
	public void endPathExpr() throws JaxenException {
		String poped = this.conditions.popCondition();
		this.conditions.popFrame();
		this.conditions.pushCondition(poped);

	}

	/**
	 * [1] LocationPath ::= RelativeLocationPath | AbsoluteLocationPath [2]
	 * AbsoluteLocationPath ::= '/' RelativeLocationPath? |
	 * AbbreviatedAbsoluteLocationPath
	 */
	@Override
	public void startAbsoluteLocationPath() throws JaxenException {
		this.conditions.pushFrame();
		try {
			this.parent = this.parent.getSession().getRootNode();
		} catch (RepositoryException e) {
			throw new JaxenException(e);
		}
	}

	@Override
	public void endAbsoluteLocationPath() throws JaxenException {
		endLocationPath();
	}

	/**
	 * RelativeLocationPath::=Step | RelativeLocationPath '/' Step |
	 * AbbreviatedRelativeLocationPath
	 * 
	 */
	@Override
	public void startRelativeLocationPath() throws JaxenException {
		this.conditions.pushFrame();

	}

	@Override
	public void endRelativeLocationPath() throws JaxenException {
		endLocationPath();
	}

	private void endLocationPath() throws JaxenException {
		this.conditions.merge();
	}

	/*
	 * 
	 * Step::=AxisSpecifier NodeTest Predicate* | AbbreviatedStep
	 * AxisSpecifier::= AxisName '::'| AbbreviatedAxisSpecifier
	 * 
	 * @see org.jaxen.saxpath.XPathHandler#startNameStep(int, java.lang.String,
	 * java.lang.String)
	 */
	@Override
	public void startNameStep(int axis, String prefix, String localName) throws JaxenException {
		switch (axis) {
		case Axis.ANCESTORORSELF:
			this.processChildStep(prefix, localName);
			break;
		case Axis.NAMESPACE:
		case Axis.ATTRIBUTE:
			this.processAttributeStep(prefix, localName);
			break;
		default:
			System.err.println("Unknown axis" + axis + " for name:" + localName);
		}
	}

	/**
	 * 
	 * @param localName
	 * @return
	 * @throws JaxenException
	 */
//	private NodeDefinition pushNodeDefinition(String localName) throws JaxenException {
//		try {
//			if (nodefinition == null)
//				nodefinition = JCRUtils.getChildNodeDefinition(this.parent.getPrimaryNodeType(), localName);
//			else
//				nodefinition = JCRUtils.getChildNodeDefinition(this.nodefinition, localName);
//		} catch (RepositoryException ex) {
//			throw new JaxenException(ex);
//		}
//		if (nodefinition == null)
//			throw new JaxenException("Unknown definition name '" + localName + "'.");
//		return nodefinition;
//	}

	/**
	 * 
	 * @param localName
	 * @return
	 * @throws JaxenException
	 */
//	private PropertyDefinition getPropertyDefinition(String localName) throws JaxenException {
//		try {
//			if (this.nodefinition == null) {
//				return JCRUtils.getPropertyDefinition(this.parent.getPrimaryNodeType(), localName);
//			} else {
//				return JCRUtils.getPropertyDefinition(nodefinition, localName);
//			}
//		} catch (RepositoryException ex) {
//			throw new JaxenException(ex);
//		}
//	}

	/**
	 * 
	 * @param prefix
	 * @param localName
	 * @throws JaxenException
	 * @throws RepositoryException
	 */
	private void processChildStep(String prefix, String localName) throws JaxenException {
		//this.pushNodeDefinition(localName);
		if (this.stepNo > 0) {
			this.condition = this.conditions.popFrame().peek();
			StringBuffer buffer = new StringBuffer();
			buffer.append("SELECT pkid FROM node ");
			for (String property : this.properties) {
				String table = QDBName.encode(property);
				buffer.append("LEFT JOIN " + table);
				buffer.append(" ON " + table + ".node_id=node.pkid ");
			}
			if (this.condition.length() > 0) {
				buffer.append(" WHERE ");
				buffer.append(this.condition);
			}
			buffer.toString();
			this.conditions.pushFrame();
			this.conditions.pushFrame();
			this.conditions.pushCondition("node.parent_id in (" + buffer.toString() + ") AND node.name=?");
			this.parameters.add(this.valueFactory.createValue(localName));
			this.properties.clear();
		} else {
			this.conditions.pushFrame();
			this.conditions.pushCondition("node.name=? AND node.parent_id=?");
			this.parameters.add(this.valueFactory.createValue(localName));
			try {
				this.parameters.add(this.valueFactory.createValue(parent.getIdentifier()));
			} catch (RepositoryException e) {
				throw new JaxenException(e);
			}
		}
		this.stepNo++;

	}

	/**
	 * 暂不支持
	 * 
	 * @param prefix
	 * @param localName
	 * @throws JaxenException
	 */
	private void processAttributeStep(String prefix, String localName) throws JaxenException {
		String table = QDBName.encode(localName);
		this.conditions.pushFrame();
		this.conditions.pushCondition(table + ".value");
		this.properties.add(localName);
	}

	/**
	 * 
	 * 
	 * @param prefix
	 * @param localName
	 * @throws JaxenException
	 */
	public void endNameStep() {
		this.conditions.merge();
	}

	/**
	 * 暂不支持
	 * 
	 * @param prefix
	 * @param localName
	 * @throws JaxenException
	 */
	public void startTextNodeStep(int axis) throws JaxenException {
		throw new UnsupportedOperationException();
	}

	/**
	 * 暂不支持
	 * 
	 * @param prefix
	 * @param localName
	 * @throws JaxenException
	 */
	public void endTextNodeStep() {
		throw new UnsupportedOperationException();
	}

	/**
	 * 暂不支持
	 * 
	 * @param prefix
	 * @param localName
	 * @throws JaxenException
	 */
	public void startCommentNodeStep(int axis) throws JaxenException {
		throw new UnsupportedOperationException();
	}

	/**
	 * 暂不支持
	 * 
	 * @param prefix
	 * @param localName
	 * @throws JaxenException
	 */
	public void endCommentNodeStep() {
		throw new UnsupportedOperationException();
	}

	/**
	 * 选取所有子节点 不需处理
	 */
	public void startAllNodeStep(int axis) throws JaxenException {
		throw new UnsupportedOperationException();
	}

	/**
	 * 不需处理
	 */
	public void endAllNodeStep() {
		throw new UnsupportedOperationException();
	}

	/**
	 * 暂不支持
	 * 
	 * @param prefix
	 * @param localName
	 * @throws JaxenException
	 */
	public void startProcessingInstructionNodeStep(int axis, String name) throws JaxenException {
		throw new UnsupportedOperationException();
	}

	/**
	 * 暂不支持
	 * 
	 * @param prefix
	 * @param localName
	 * @throws JaxenException
	 */
	public void endProcessingInstructionNodeStep() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Predicates： 使用[]框起来的节点过滤条件 Predicates are used to find a specific node or
	 * a node that contains a specific value. Predicates are always embedded in
	 * square brackets. In the table below we have listed some path expressions
	 * with predicates and the result of the expressions:
	 */
	public void startPredicate() {
		this.conditions.pushFrame();
	}

	public void endPredicate() throws JaxenException {
		// 如果堆栈顶部是个整数，说明是简单的顺序选择器；
		String top = this.conditions.popCondition();
		if (top.length() == 1)
			top = ("node.vindex=?");
		this.conditions.popFrame();
		this.conditions.pushCondition(top);
	}

	public void startFilterExpr() {
		this.conditions.pushFrame();
	}

	public void endFilterExpr() throws JaxenException {
		this.conditions.merge();
	}

	protected void returnExpr() {
		String cond = this.conditions.popCondition();
		this.conditions.popFrame();
		this.conditions.pushCondition(cond);
	}

	public void startOrExpr() {

	}

	public void endOrExpr(boolean create) throws JaxenException {
		if (create) {
			StringBuffer or = new StringBuffer();
			String right = this.conditions.popCondition();
			String left = this.conditions.popCondition();
			or.append("(").append(left).append(") OR (").append(right).append(")");
			this.conditions.pushCondition(or.toString());
		}
	}

	public void startAndExpr() {
	}

	public void endAndExpr(boolean create) throws JaxenException {
		if (create) {
			StringBuffer or = new StringBuffer();
			String right = this.conditions.popCondition();
			String left = this.conditions.popCondition();
			or.append("(").append(left).append(") AND (").append(right).append(")");
			this.conditions.pushCondition(or.toString());
		}
	}

	public void startEqualityExpr() {
	}

	public void endEqualityExpr(int operator) throws JaxenException {
		if (operator != Operator.NO_OP) {
			String right = this.conditions.popCondition();
			String left = this.conditions.popCondition();

			StringBuffer buffer = new StringBuffer("");
			buffer.append(left);
			switch (operator) {
			case Operator.EQUALS:
				buffer.append("=");
				break;
			case Operator.NOT_EQUALS:
				buffer.append("<>");
				break;
			}
			buffer.append(right);
			this.conditions.pushCondition(buffer.toString());
		}

	}

	public void startRelationalExpr() {
	}

	public void endRelationalExpr(int operator) throws JaxenException {
		if (operator != Operator.NO_OP) {
			String right = this.conditions.popCondition();
			String left = this.conditions.popCondition();
			//PropertyDefinition definition = this.getPropertyDefinition(left);

			//int type = ((PropertyDefinition) definition).getRequiredType();
			Value value = valueFactory.createValue(right);
	
			String table = QDBName.encode(left);
			StringBuffer buffer = new StringBuffer("(");
			buffer.append(table).append(".value");
			switch (operator) {
			case Operator.GREATER_THAN:
				buffer.append(">");
				break;
			case Operator.GREATER_THAN_EQUALS:
				buffer.append(">=");
				break;
			case Operator.LESS_THAN:
				buffer.append("<");
				break;
			case Operator.LESS_THAN_EQUALS:
				buffer.append("<=");
				break;
			case Operator.NOT_EQUALS:
				buffer.append("<>");
				break;
			case Operator.EQUALS:
				buffer.append("=");
				break;
			}
			buffer.append("?)");
			this.parameters.add(value);
			this.conditions.pushCondition(buffer.toString());
		}
	}

	public void startAdditiveExpr() {
	}

	public void endAdditiveExpr(int operator) throws JaxenException {

		String right = this.conditions.popCondition();
		String left = this.conditions.popCondition();
		StringBuffer buffer = new StringBuffer();
		buffer.append(left);
		switch (operator) {
		case Operator.ADD:
			buffer.append("+");
			break;
		case Operator.SUBTRACT:
			buffer.append("-");
			break;
		}
		buffer.append(right);
		this.conditions.pushCondition(buffer.toString());
	}

	public void startMultiplicativeExpr() {
	}

	public void endMultiplicativeExpr(int operator) throws JaxenException {
		String right = this.conditions.popCondition();
		String left = this.conditions.popCondition();
		StringBuffer buffer = new StringBuffer();
		buffer.append(left);
		switch (operator) {
		case Operator.MULTIPLY:
			buffer.append("*");
			break;
		case Operator.DIV:
			buffer.append("/");
			break;
		case Operator.MOD:
			buffer.append(" mod ");
			break;
		}
		buffer.append(right);
		this.conditions.pushCondition(buffer.toString());
	}

	public void startUnaryExpr() {
	}

	public void endUnaryExpr(int operator) throws JaxenException {
		String right = this.conditions.popCondition();
		StringBuffer buffer = new StringBuffer();
		switch (operator) {
		case Operator.NO_OP:
			buffer.append("!");
			break;
		}
		buffer.append(right);
		this.conditions.pushCondition(buffer.toString());
	}

	public void startUnionExpr() {
	}

	/**
	 * 暂不支持
	 */
	public void endUnionExpr(boolean create) throws JaxenException {

	}

	public void number(int number) throws JaxenException {
		this.conditions.pushCondition("?");
		Value value = valueFactory.createValue(number);
		this.parameters.add(value);
	}

	public void number(double number) throws JaxenException {
		this.conditions.pushCondition("?");
		Value value = valueFactory.createValue(number);
		this.parameters.add(value);

	}

	public void literal(String literal) throws JaxenException {
		this.conditions.pushCondition("?");
//		String definition = this.properties.peek();
//		int type = ((PropertyDefinition) definition).getRequiredType();
		Value value = valueFactory.createValue(literal);
		this.parameters.add(value);

	}

	public void variableReference(String prefix, String variableName) throws JaxenException {
		this.conditions.pushCondition(variableName);

	}

	/**
	 * 暂不支持
	 */
	public void startFunction(String prefix, String functionName) throws JaxenException {
	}

	/**
	 * 暂不支持
	 */
	public void endFunction() {

	}
	
	
}
